/*
 * @(#)PaletteTabbedPaneUI.java
 * 
 * Copyright (c) 2010 by the original authors of JHotDraw and all its
 * contributors. All rights reserved.
 * 
 * You may not use, copy or modify this file, except in compliance with the 
 * license agreement you entered into with the copyright holders. For details
 * see accompanying license terms.
 */
package org.jhotdraw.gui.plaf.palette;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalTabbedPaneUI;
import javax.swing.text.View;

/**
 * PaletteTabbedPaneUI.
 *
 * @author Werner Randelshofer
 * @version $Id: PaletteTabbedPaneUI.java 717 2010-11-21 12:30:57Z rawcoder $
 */
public class PaletteTabbedPaneUI extends BasicTabbedPaneUI {

    private final static float[] enabledStops = new float[]{0f, 0.35f, 0.4f, 1f};
    private final static Color[] enabledStopColors = new Color[]{new Color(0xf8f8f8), new Color(0xeeeeee), new Color(0xcacaca), new Color(0xffffff)};
    private boolean tabsOverlapBorder;
    private Color selectedColor;
    private boolean tabsOpaque = true;
    private boolean contentOpaque = true;

    public static ComponentUI createUI(JComponent c) {
        return new PaletteTabbedPaneUI();
    }

    @Override
    protected void installDefaults() {
        PaletteLookAndFeel laf = PaletteLookAndFeel.getInstance();
        MetalTabbedPaneUI mtpui;
        PaletteLookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
                "TabbedPane.foreground", "TabbedPane.font");
        highlight = laf.getColor("TabbedPane.light");
        lightHighlight = laf.getColor("TabbedPane.highlight");
        shadow = laf.getColor("TabbedPane.shadow");
        darkShadow = laf.getColor("TabbedPane.darkShadow");
        focus = laf.getColor("TabbedPane.focus");
        selectedColor = laf.getColor("TabbedPane.selected"); // will probably not
        // work as expected since we don't override enough colors from BasicTabbedPaneUI

        textIconGap = laf.getInt("TabbedPane.textIconGap");
        tabInsets = laf.getInsets("TabbedPane.tabInsets");
        selectedTabPadInsets = laf.getInsets("TabbedPane.selectedTabPadInsets");
        tabAreaInsets = laf.getInsets("TabbedPane.tabAreaInsets");
        tabsOverlapBorder = laf.getBoolean("TabbedPane.tabsOverlapBorder");
        ;  // will probably not
        // work as expected since we don't override enough colors from BasicTabbedPaneUI
        contentBorderInsets = laf.getInsets("TabbedPane.contentBorderInsets");
        tabRunOverlay = laf.getInt("TabbedPane.tabRunOverlay");
        tabsOpaque = laf.getBoolean("TabbedPane.tabsOpaque");// will probably not
        // work as expected since we don't override enough colors from BasicTabbedPaneUI
        contentOpaque = laf.getBoolean("TabbedPane.contentOpaque");// will probably not
        // work as expected since we don't override enough colors from BasicTabbedPaneUI
        Object opaque = laf.get("TabbedPane.opaque");
        if (opaque == null) {
            opaque = Boolean.FALSE;
        }
        LookAndFeel.installProperty(tabPane, "opaque", opaque);

        // Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these
        // keys are missing. So we are setting them to there default values here
        // if the keys are missing.
        if (tabInsets == null) {
            tabInsets = new Insets(0, 4, 1, 4);
        }
        if (selectedTabPadInsets == null) {
            selectedTabPadInsets = new Insets(2, 2, 2, 1);
        }
        if (tabAreaInsets == null) {
            tabAreaInsets = new Insets(3, 2, 0, 2);
        }
        if (contentBorderInsets == null) {
            contentBorderInsets = new Insets(2, 2, 3, 3);
        }
    }

    @Override
    protected void paintTab(Graphics g, int tabPlacement,
            Rectangle[] rects, int tabIndex,
            Rectangle iconRect, Rectangle textRect) {
        Rectangle tabRect = rects[tabIndex];
        int selectedIndex = tabPane.getSelectedIndex();
        boolean isSelected = selectedIndex == tabIndex;

        if (tabsOpaque || tabPane.isOpaque()) {
            paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
                    tabRect.width, tabRect.height, isSelected);
        }

        paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
                tabRect.width, tabRect.height, isSelected);

        String title = tabPane.getTitleAt(tabIndex);
        Font font = isSelected ? PaletteLookAndFeel.getInstance().getFont("TabbedPane.selectedFont") : tabPane.getFont();
        FontMetrics metrics = PaletteUtilities.getFontMetrics(tabPane, g, font);
        Icon icon = getIconForTab(tabIndex);

        layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
                tabRect, iconRect, textRect, isSelected);

        if (tabPane.getTabComponentAt(tabIndex) == null) {
            String clippedTitle = title;

            // XXX - Implement scrollable tab layout support.
            /*
            if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() &&
            tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) {
            int availTextWidth = tabScroller.croppedEdge.getCropline() -
            (textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth();
            clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
            }*/

            paintText(g, tabPlacement, font, metrics,
                    tabIndex, clippedTitle, textRect, isSelected);

            paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
        }
        paintFocusIndicator(g, tabPlacement, rects, tabIndex,
                iconRect, textRect, isSelected);
    }

    /**
     * this function draws the border around each tab
     * note that this function does now draw the background of the tab.
     * that is done elsewhere
     */
    @Override
    protected void paintTabBorder(Graphics g, int tabPlacement,
            int tabIndex,
            int x, int y, int w, int h,
            boolean isSelected) {
        g.setColor(isSelected ? darkShadow : shadow);

        switch (tabPlacement) {
            case LEFT:
                g.drawLine(x + 1, y + h - 2, x + 1, y + h - 2); // bottom-left highlight
                g.drawLine(x, y + 2, x, y + h - 3); // left highlight
                g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left highlight
                g.drawLine(x + 2, y, x + w - 1, y); // top highlight

                g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); // bottom shadow

                g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); // bottom dark shadow
                break;
            case RIGHT:
                g.drawLine(x, y, x + w - 3, y); // top highlight

                g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); // bottom shadow
                g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); // right shadow

                g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right dark shadow
                g.drawLine(x + w - 2, y + h - 2, x + w - 2, y + h - 2); // bottom-right dark shadow
                g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 3); // right dark shadow
                g.drawLine(x, y + h - 1, x + w - 3, y + h - 1); // bottom dark shadow
                break;
            case BOTTOM:
                g.drawLine(x, y, x, y + h - 2); // left highlight
//              g.drawLine(x+1, y+h-1, x+1, y+h-1); // bottom-left highlight

                g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2); // bottom shadow
                g.drawLine(x + w - 1, y, x + w - 1, y + h - 2); // right shadow

                /*
                g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
                g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
                g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
                 */
                break;
            case TOP:
            default:
                g.drawLine(x, y + 2, x, y + h - 1); // left highlight
                g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left highlight
                g.drawLine(x + 2, y, x + w - 3, y); // top highlight

                g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 1); // right shadow

                g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 1); // right dark-shadow
                g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right shadow
        }
    }

    @Override
    protected void paintTabBackground(Graphics gr, int tabPlacement,
            int tabIndex,
            int x, int y, int w, int h,
            boolean isSelected) {
        Graphics2D g = (Graphics2D) gr;
        g.setColor(!isSelected || selectedColor == null
                ? tabPane.getBackgroundAt(tabIndex) : selectedColor);
        switch (tabPlacement) {
            case LEFT:
                g.fillRect(x + 1, y + 1, w - 1, h - 3);
                break;
            case RIGHT:
                g.fillRect(x, y + 1, w - 2, h - 3);
                break;
            case BOTTOM:
                if (!isSelected) {
                    LinearGradientPaint lgp = new LinearGradientPaint(
                            new Point2D.Float(x, y), new Point2D.Float(x, y + h - 1),
                            enabledStops, enabledStopColors,
                            MultipleGradientPaint.CycleMethod.REPEAT);
                    g.setPaint(lgp);
                }
                g.fillRect(x + 1, y, w - 3, h - 1);
                break;
            case TOP:
            default:
                g.fillRect(x + 1, y + 1, w - 3, h - 1);
        }
    }

    @Override
    protected void paintText(Graphics g, int tabPlacement,
            Font font, FontMetrics metrics, int tabIndex,
            String title, Rectangle textRect,
            boolean isSelected) {

        g.setFont(font);

        View v = getTextViewForTab(tabIndex);
        if (v != null) {
            // html
            v.paint(g, textRect);
        } else {
            // plain text
            int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);

            if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
                Color fg = tabPane.getForegroundAt(tabIndex);
                if (isSelected && (fg instanceof UIResource)) {
                    Color selectedFG = PaletteLookAndFeel.getInstance().getColor(
                            "TabbedPane.selectedForeground");
                    if (selectedFG != null) {
                        fg = selectedFG;
                    }
                }
                g.setColor(fg);
                PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
                        title, mnemIndex,
                        textRect.x, textRect.y + metrics.getAscent());

            } else { // tab disabled
                g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
                PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
                        title, mnemIndex,
                        textRect.x, textRect.y + metrics.getAscent());
                g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
                PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
                        title, mnemIndex,
                        textRect.x - 1, textRect.y + metrics.getAscent() - 1);

            }
        }
    }

    @Override
    protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
        int width = tabPane.getWidth();
        int height = tabPane.getHeight();
        Insets insets = tabPane.getInsets();
        Insets tabAreaInsets = getTabAreaInsets(tabPlacement);

        int x = insets.left;
        int y = insets.top;
        int w = width - insets.right - insets.left;
        int h = height - insets.top - insets.bottom;

        switch (tabPlacement) {
            case LEFT:
                x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                if (tabsOverlapBorder) {
                    x -= tabAreaInsets.right;
                }
                w -= (x - insets.left);
                break;
            case RIGHT:
                w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
                if (tabsOverlapBorder) {
                    w += tabAreaInsets.left;
                }
                break;
            case BOTTOM:
                h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                if (tabsOverlapBorder) {
                    h += tabAreaInsets.top;
                }
                break;
            case TOP:
            default:
                y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
                if (tabsOverlapBorder) {
                    y -= tabAreaInsets.bottom;
                }
                h -= (y - insets.top);
        }

        if (tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque())) {
            // Fill region behind content area
            Color color = PaletteLookAndFeel.getInstance().getColor("TabbedPane.contentAreaColor");
            if (color != null) {
                g.setColor(color);
            } else if (selectedColor == null || selectedIndex == -1) {
                g.setColor(tabPane.getBackground());
            } else {
                g.setColor(selectedColor);
            }
            g.fillRect(x, y, w, h);
        }

        boolean paintBorder = tabPane.getClientProperty("Palette.TabbedPane.paintContentBorder") != Boolean.FALSE;

        if (tabPlacement == TOP || paintBorder) {
            paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        }
        if (tabPlacement == LEFT || paintBorder) {
            paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        }
        if (tabPlacement == BOTTOM || paintBorder) {
            paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        }
        if (tabPlacement == RIGHT || paintBorder) {
            paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        }

    }

    @Override
    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
            int selectedIndex,
            int x, int y, int w, int h) {
        Rectangle selRect = selectedIndex < 0 ? null
                : getTabBounds(selectedIndex, calcRect);

        g.setColor(lightHighlight);

        // Draw unbroken line if tabs are not on TOP, OR
        // selected tab is not in run adjacent to content, OR
        // selected tab is not visible (SCROLL_TAB_LAYOUT)
        //
        if (tabPlacement != TOP || selectedIndex < 0
                || (selRect.y + selRect.height + 1 < y)
                || (selRect.x < x || selRect.x > x + w)) {
            g.drawLine(x, y, x + w - 2, y);
        } else {
            // Break line to show visual connection to selected tab
            g.drawLine(x, y, selRect.x - 1, y);
            if (selRect.x + selRect.width < x + w - 2) {
                g.drawLine(selRect.x + selRect.width, y,
                        x + w - 2, y);
            } else {
                g.setColor(shadow);
                g.drawLine(x + w - 2, y, x + w - 2, y);
            }
        }
    }

    @Override
    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
            int selectedIndex,
            int x, int y, int w, int h) {
        Rectangle selRect = selectedIndex < 0 ? null
                : getTabBounds(selectedIndex, calcRect);

        g.setColor(lightHighlight);

        // Draw unbroken line if tabs are not on LEFT, OR
        // selected tab is not in run adjacent to content, OR
        // selected tab is not visible (SCROLL_TAB_LAYOUT)
        //
        if (tabPlacement != LEFT || selectedIndex < 0
                || (selRect.x + selRect.width + 1 < x)
                || (selRect.y < y || selRect.y > y + h)) {
            g.drawLine(x, y, x, y + h - 2);
        } else {
            // Break line to show visual connection to selected tab
            g.drawLine(x, y, x, selRect.y - 1);
            if (selRect.y + selRect.height < y + h - 2) {
                g.drawLine(x, selRect.y + selRect.height,
                        x, y + h - 2);
            }
        }
    }

    @Override
    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
            int selectedIndex,
            int x, int y, int w, int h) {
        Rectangle selRect = selectedIndex < 0 ? null
                : getTabBounds(selectedIndex, calcRect);

        g.setColor(shadow);

        // Draw unbroken line if tabs are not on BOTTOM, OR
        // selected tab is not in run adjacent to content, OR
        // selected tab is not visible (SCROLL_TAB_LAYOUT)
        //
        if (tabPlacement != BOTTOM || selectedIndex < 0
                || (selRect.y - 1 > h)
                || (selRect.x < x || selRect.x > x + w)) {
            //g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2);
            g.setColor(shadow);
            g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
        } else {
            // Break line to show visual connection to selected tab
            //g.drawLine(x + 1, y + h - 2, selRect.x - 1, y + h - 2);
            g.setColor(shadow);
            g.drawLine(x, y + h - 1, selRect.x , y + h - 1);
            if (selRect.x + selRect.width < x + w - 2) {
                //g.setColor(shadow);
                //g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
                g.setColor(shadow);
                g.drawLine(selRect.x + selRect.width-1, y + h - 1, x + w - 1, y + h - 1);
            }
        }

    }

    @Override
    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
            int selectedIndex,
            int x, int y, int w, int h) {
        Rectangle selRect = selectedIndex < 0 ? null
                : getTabBounds(selectedIndex, calcRect);

        g.setColor(shadow);

        // Draw unbroken line if tabs are not on RIGHT, OR
        // selected tab is not in run adjacent to content, OR
        // selected tab is not visible (SCROLL_TAB_LAYOUT)
        //
        if (tabPlacement != RIGHT || selectedIndex < 0
                || (selRect.x - 1 > w)
                || (selRect.y < y || selRect.y > y + h)) {
            g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 3);
            g.setColor(darkShadow);
            g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
        } else {
            // Break line to show visual connection to selected tab
            g.drawLine(x + w - 2, y + 1, x + w - 2, selRect.y - 1);
            g.setColor(darkShadow);
            g.drawLine(x + w - 1, y, x + w - 1, selRect.y - 1);

            if (selRect.y + selRect.height < y + h - 2) {
                g.setColor(shadow);
                g.drawLine(x + w - 2, selRect.y + selRect.height,
                        x + w - 2, y + h - 2);
                g.setColor(darkShadow);
                g.drawLine(x + w - 1, selRect.y + selRect.height,
                        x + w - 1, y + h - 2);
            }
        }
    }

    @Override
    protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
        Rectangle tabRect = rects[tabIndex];
        int nudge = 0;
        switch (tabPlacement) {
            case LEFT:
                nudge = isSelected ? -1 : 1;
                break;
            case RIGHT:
                nudge = isSelected ? 1 : -1;
                break;
            case BOTTOM:
            case TOP:
            default:
                nudge = tabRect.width % 2;
        }
        return nudge;
    }

    @Override
    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
        Rectangle tabRect = rects[tabIndex];
        int nudge = 0;
        switch (tabPlacement) {
            case BOTTOM:
                //nudge = isSelected? 1 : -1;
                nudge = -1;
                break;
            case LEFT:
            case RIGHT:
                nudge = tabRect.height % 2;
                break;
            case TOP:
            default:
                nudge = isSelected ? -1 : 1;
        }
        return nudge;
    }

    private boolean scrollableTabLayoutEnabled() {
        return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
    }

    @Override
    protected LayoutManager createLayoutManager() {
        if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
            return super.createLayoutManager();
        }
        return new TabbedPaneLayout();
    }
    @Override
    protected FontMetrics getFontMetrics() {
        //Font font = tabPane.getFont();
        Font font = PaletteLookAndFeel.getInstance().getFont("TabbedPane.selectedFont");
        return tabPane.getFontMetrics(font);
    }

    protected class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {

        public TabbedPaneLayout() {
            PaletteTabbedPaneUI.this.super();
        }

        @Override
        protected void normalizeTabRuns(int tabPlacement, int tabCount,
                int start, int max) {
            // Only normalize the runs for top & bottom;  normalizing
            // doesn't look right for Metal's vertical tabs
            // because the last run isn't padded and it looks odd to have
            // fat tabs in the first vertical runs, but slimmer ones in the
            // last (this effect isn't noticeable for horizontal tabs).
            if (tabPlacement == TOP || tabPlacement == BOTTOM) {
                super.normalizeTabRuns(tabPlacement, tabCount, start, max);
            }
        }

        // Don't rotate runs!
        @Override
        protected void rotateTabRuns(int tabPlacement, int selectedRun) {
        }

        // Don't pad selected tab
        @Override
        protected void padSelectedTab(int tabPlacement, int selectedIndex) {
        }
    }
}
